﻿using System;
using System.IO;
using EnvDTE;

namespace Neovolve.Extensibility.VisualStudio.TestProfilerLink
{
    internal class TestFrameworkProcessor
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TestFrameworkProcessor"/> class.
        /// </summary>
        /// <param name="framework">The framework.</param>
        public TestFrameworkProcessor(TestFramework framework)
        {
            Framework = framework;
        }

        public String Build(Solution solution)
        {
            throw new NotImplementedException();
        }

        public String Build(Project project)
        {
            throw new NotImplementedException();
        }

        public String Build(ProjectItem item)
        {
            throw new NotImplementedException();
        }

        public String Build(CodeElement element)
        {
            // Ensure that the UnitTestUsage property has a value
            if (String.IsNullOrEmpty(Framework.UnitTestUsage))
            {
                throw new TestFrameworkConfigurationException(Framework.Name, "UnitTestUsage");
            }

            throw new NotImplementedException();
        }

        private String Build(Project[] projects, ProjectItem[] items, CodeElement[] elements)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets the is supported.
        /// </summary>
        /// <param name="framework">The framework.</param>
        /// <param name="solution">The solution.</param>
        /// <returns></returns>
        public static Boolean GetIsSupported(TestFramework framework, Solution solution)
        {
            // Checks whether the framework parameter has been supplied
            if (framework == null)
            {
                const String FrameworkParameterName = "framework";

                throw new ArgumentNullException(FrameworkParameterName);
            }

            // Checks whether the solution parameter has been supplied
            if (solution == null)
            {
                const String SolutionParameterName = "solution";

                throw new ArgumentNullException(SolutionParameterName);
            }

            // Loop through each project
            foreach (Project project in solution.Projects)
            {
                // Check if the framework supports this project 
                if (GetIsSupported(framework, project))
                {
                    return true;
                }
            }

            // No projects in the solution are supported
            return false;
        }

        /// <summary>
        /// Gets the is supported.
        /// </summary>
        /// <param name="framework">The framework.</param>
        /// <param name="project">The project.</param>
        /// <returns></returns>
        public static Boolean GetIsSupported(TestFramework framework, Project project)
        {
            // Checks whether the framework parameter has been supplied
            if (framework == null)
            {
                const String FrameworkParameterName = "framework";

                throw new ArgumentNullException(FrameworkParameterName);
            }

            // Checks whether the project parameter has been supplied
            if (project == null)
            {
                const String ProjectParameterName = "project";

                throw new ArgumentNullException(ProjectParameterName);
            }

            // Check if there is a project guid filter defined on the framework
            if (String.IsNullOrEmpty(framework.TestAssemblyProjectGuid) == false)
            {
                // Check if the project contains this guid
                if (project.Kind.Contains(framework.TestAssemblyProjectGuid) == false)
                {
                    // This project doesn't contain the required guid type
                    return false;
                }
            }

            // Loop through each file in the project
            foreach (ProjectItem item in project.ProjectItems)
            {
                // Check if the framework supports the project item
                if (GetIsSupported(framework, item))
                {
                    return true;
                }
            }

            // No items in the project were supported
            return false;
        }

        private static Boolean GetIsSupported(TestFramework framework, ProjectItem item)
        {
            // Check if the file is a load test
            if (framework.IdentifyLoadTestByAttribute == false
                && String.IsNullOrEmpty(framework.LoadTestExtension) == false)
            {
                // Loop through each file in the item
                for (Int16 index = 0; index < item.FileCount; index++)
                {
                    // Check if the file extension matches the load test file
                    if (String.Compare(framework.LoadTestExtension, Path.GetExtension(item.get_FileNames(index)), true)
                        == 0)
                    {
                        return true;
                    }
                }
            }

            // Exit if the file does't contain a code model
            if (item.FileCodeModel == null)
            {
                return false;
            }

            // Loop through each element in the item
            foreach (CodeElement element in item.FileCodeModel.CodeElements)
            {
                // Check if this element is a class
                if (element.Kind == vsCMElement.vsCMElementClass
                    && element is CodeClass)
                {
                    // Check if the code class is supported
                    if (GetIsSupported(framework, element as CodeClass))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Gets the is supported.
        /// </summary>
        /// <param name="framework">The framework.</param>
        /// <param name="classItem">The class item.</param>
        /// <returns></returns>
        private static Boolean GetIsSupported(TestFramework framework, CodeClass classItem)
        {
            // Check if there is a class attribute to check
            if (String.IsNullOrEmpty(framework.TestClassAttributeType) == false)
            {
                // Check if this item contains a class that is marked with the required attribute
                Boolean attributeExists = false;

                // Loop through each attribute on the class
                foreach (CodeElement element in classItem.Attributes)
                {
                    CodeAttribute attribute = element as CodeAttribute;

                    // Check if the attribute exists
                    if (attribute == null)
                    {
                        continue;
                    }

                    // Check if the attribute name matches
                    if (attribute.Value
                        == framework.TestClassAttributeType)
                    {
                        attributeExists = true;

                        break;
                    }
                }

                // Check if the required attribute was found
                if (attributeExists == false)
                {
                    // The attribute wasn't found, this class is not supported
                    return false;
                }
            }

            // If there is no attribute defined, the default is that the class is testable

            // Check if there is a method attribute to check
            if (String.IsNullOrEmpty(framework.TestMethodAttributeType) == false)
            {
                // Loop through each method in the class
                foreach (CodeElement element in classItem.Members)
                {
                    CodeFunction functionItem = element as CodeFunction;

                    // Skip to the next item if this is not a function
                    if (functionItem == null)
                    {
                        continue;
                    }

                    if (GetIsSupported(framework, functionItem))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static Boolean GetIsSupported(TestFramework framework, CodeFunction functionItem)
        {
            // Check if there is a method attribute to check
            if (String.IsNullOrEmpty(framework.TestMethodAttributeType))
            {
                // If there is no attribute defined, the default is that the function is testable
                return true;
            }

            // Loop through each attribute on the class
            foreach (CodeElement element in functionItem.Attributes)
            {
                CodeAttribute attribute = element as CodeAttribute;

                // Check if the attribute exists
                if (attribute == null)
                {
                    continue;
                }

                // Check if the attribute name matches
                if (attribute.Value
                    == framework.TestMethodAttributeType)
                {
                    return true;
                }
            }

            // Return whether the attribute was found
            return false;
        }

        /// <summary>
        /// Gets or sets the framework.
        /// </summary>
        /// <value>The framework.</value>
        private TestFramework Framework
        {
            get;
            set;
        }
    }
}